package bjut.zrpc.core.optimization.proxy;


import bjut.zrpc.core.optimization.bean.ZRpcServiceBean;
import bjut.zrpc.core.optimization.enums.ZRpcConfigEnum;
import bjut.zrpc.core.optimization.enums.ZRpcResponseEnum;
import bjut.zrpc.core.optimization.event.*;
import bjut.zrpc.core.optimization.extension.ExtensionLoader;
import bjut.zrpc.core.optimization.factory.SingletonFactory;
import bjut.zrpc.core.optimization.interfaces.ServiceDiscover;
import bjut.zrpc.core.optimization.interfaces.ZRpcRequestTransport;
import bjut.zrpc.core.optimization.protect.ServicesRecord;
import bjut.zrpc.core.optimization.message.ZRpcRequestMessage;
import bjut.zrpc.core.optimization.message.ZRpcResponseMessage;
import bjut.zrpc.core.optimization.netty.NettyClient;
import bjut.zrpc.core.optimization.utils.PropertiesReadUtil;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @Author: zhuzhiming
 * @Date: 2022/6/24 13:48
 */
public class ZRpcClientJdkProxy implements ProxyFactory, InvocationHandler, ZRpcEventPublisher {

    private final ZRpcServiceBean zRpcServiceBean;
    private final ZRpcRequestTransport zRpcRequestTransport;
    private final ServiceDiscover serviceDiscover;
    private final ServicesRecord servicesRecord;
    private static final String MOCK_METHOD_NAME = "mockMethod";
    private boolean isFirst = true;
    private boolean isMock = false;
    private SimpleZRpcEventMulticaster simpleZRpcEventMulticaster;

    public ZRpcClientJdkProxy(ZRpcRequestTransport zRpcRequestTransport, ZRpcServiceBean zRpcServiceBean) {
        this.zRpcRequestTransport = zRpcRequestTransport;
        this.zRpcServiceBean = zRpcServiceBean;
        String discover = PropertiesReadUtil.getConfig(ZRpcConfigEnum.ZRPC_SERVICE_DISCOVER.getPropertyValue()[0]);
        serviceDiscover = ExtensionLoader.getExtensionLoader(ServiceDiscover.class).getExtension(discover);
        servicesRecord = SingletonFactory.getInstance(ServicesRecord.class);
        simpleZRpcEventMulticaster = SingletonFactory.getInstance(SimpleZRpcEventMulticaster.class);
    }

    @Override
    public <T> T createProxy(Class<T> cls) {

        return (T) Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, this);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        publishEvent(new RemoteInvokeBeforeEvent(this));
        Object result = remoteInvoke(method, args);
        publishEvent(new RemoteInvokeAfterEvent(this));
        return result;
    }

    private Object remoteInvoke(Method method, Object[] args) {
        ZRpcRequestMessage rpcRequestMessage = ZRpcRequestMessage.builder()
                .requestId(UUID.randomUUID().toString())
                .interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .group(zRpcServiceBean.getGroup())
                .version(zRpcServiceBean.getVersion())
                .parameters(args)
                .parametersType(method.getParameterTypes())
                .build();

        List<String> serviceList = ServicesRecord.SERVICES_MAP.get(rpcRequestMessage.getServiceInfo());
        if (!isFirst && (serviceList == null || serviceList.size() == 0)) {
            if (zRpcServiceBean.isMock()) {
                isMock = true;
                return doMockMethod();
            }
            throw new RuntimeException("no available service");
        }

        isFirst = false;

        ZRpcResponseMessage<Object> responseMessage = null;

        servicesRecord.checkSentinel(rpcRequestMessage.getServiceInfo(), zRpcServiceBean.getDuration());

        InetSocketAddress inetSocketAddress = serviceDiscover.lookupService(rpcRequestMessage);

        if (zRpcRequestTransport instanceof NettyClient) {
            CompletableFuture<ZRpcResponseMessage<Object>> resultFuture = (CompletableFuture<ZRpcResponseMessage<Object>>) zRpcRequestTransport.sendRpcRequest(rpcRequestMessage, inetSocketAddress);
            try {
                responseMessage = resultFuture.get(zRpcServiceBean.getTimeout(), TimeUnit.MILLISECONDS);
            } catch (InterruptedException | ExecutionException | TimeoutException e) {

                if (e.getClass() == TimeoutException.class) {
                    return invokeTimeout(e, resultFuture, rpcRequestMessage, zRpcServiceBean);
                } else if (e.getClass() == ExecutionException.class) {
                    return invokeExecution(e, inetSocketAddress, rpcRequestMessage, zRpcServiceBean);
                }

            }
        }

        checkResult(rpcRequestMessage, responseMessage);

        return responseMessage.getData();
    }

    private Object invokeExecution(Exception e, InetSocketAddress inetSocketAddress, ZRpcRequestMessage rpcRequestMessage, ZRpcServiceBean zRpcServiceBean) {

        boolean needSentinel = servicesRecord.needSentinel(rpcRequestMessage.getServiceInfo() + "-" + inetSocketAddress, zRpcServiceBean.getExceptionCount(), zRpcServiceBean.getInTime());

        if (needSentinel) {
            if (zRpcServiceBean.isMock()) {
                return doMockMethod();
            }
            throw new RuntimeException("service is die");
        }

        return e.getMessage();
    }


    private Object invokeTimeout(Exception e, CompletableFuture<ZRpcResponseMessage<Object>> resultFuture, ZRpcRequestMessage rpcRequestMessage, ZRpcServiceBean zRpcServiceBean) {

        List<String> serviceList = ServicesRecord.SERVICES_MAP.get(rpcRequestMessage.getServiceInfo());
        int retry = zRpcServiceBean.getRetry();

        if (retry == 1) {
            if (zRpcServiceBean.isMock()) {
                return doMockMethod();
            }
            throw new RuntimeException(e.getMessage());
        } else if (retry == -1 && serviceList.size() > 1) {
            Iterator<String> iterator = serviceList.iterator();
            while (iterator.hasNext()) {
                return recall(e, rpcRequestMessage, zRpcServiceBean, iterator);
            }
            if (zRpcServiceBean.isMock()) {
                return doMockMethod();
            }
            throw new RuntimeException(e.getMessage());
        } else if (retry == -1 && serviceList.size() == 1) {
            try {
                return resultFuture.get(zRpcServiceBean.getTimeout() + zRpcServiceBean.getIncrease(), TimeUnit.MILLISECONDS).getData();
            } catch (InterruptedException | ExecutionException | TimeoutException ex) {
                servicesRecord.removeFirst(rpcRequestMessage.getServiceInfo());
                if (zRpcServiceBean.isMock()) {
                    return doMockMethod();
                }
                throw new RuntimeException(ex.getMessage());
            }
        } else if (retry > 1) {
            return recallByCount(e, rpcRequestMessage, zRpcServiceBean, retry);
        }

        throw new IllegalArgumentException("want to retry  but the retry count not invalid");

    }

    private Object recallByCount(Exception e, ZRpcRequestMessage rpcRequestMessage, ZRpcServiceBean zRpcServiceBean, int retry) {
        Iterator<String> iterator = ServicesRecord.SERVICES_MAP.get(rpcRequestMessage.getServiceInfo()).iterator();
        while (retry > 0 && iterator.hasNext()) {
            retry--;
            CompletableFuture<ZRpcResponseMessage<Object>> resultFuture;
            String[] serviceAddress = iterator.next().split(":");
            InetSocketAddress inetSocketAddress = new InetSocketAddress(serviceAddress[0], Integer.parseInt(serviceAddress[1]));
            resultFuture = (CompletableFuture<ZRpcResponseMessage<Object>>) zRpcRequestTransport.sendRpcRequest(rpcRequestMessage, inetSocketAddress);
            try {
                return resultFuture.get(zRpcServiceBean.getTimeout() + zRpcServiceBean.getIncrease(), TimeUnit.MILLISECONDS).getData();
            } catch (InterruptedException | ExecutionException | TimeoutException ex) {
                servicesRecord.addUnhealthyService(rpcRequestMessage.getServiceInfo(), inetSocketAddress, true);
                iterator.remove();
                return recallByCount(e, rpcRequestMessage, zRpcServiceBean, retry);
            }
        }
        if (zRpcServiceBean.isMock()) {
            return doMockMethod();
        }
        throw new RuntimeException(e.getMessage());
    }

    private Object recall(Exception e, ZRpcRequestMessage rpcRequestMessage, ZRpcServiceBean zRpcServiceBean, Iterator<String> iterator) {
        CompletableFuture<ZRpcResponseMessage<Object>> resultFuture;
        String[] serviceAddress = iterator.next().split(":");
        InetSocketAddress inetSocketAddress = new InetSocketAddress(serviceAddress[0], Integer.parseInt(serviceAddress[1]));
        resultFuture = (CompletableFuture<ZRpcResponseMessage<Object>>) zRpcRequestTransport.sendRpcRequest(rpcRequestMessage, inetSocketAddress);
        try {
            return resultFuture.get(zRpcServiceBean.getTimeout() + zRpcServiceBean.getIncrease(), TimeUnit.MILLISECONDS).getData();
        } catch (InterruptedException | ExecutionException | TimeoutException ex) {
            servicesRecord.addUnhealthyService(rpcRequestMessage.getServiceInfo(), inetSocketAddress, true);
            iterator.remove();
            return invokeTimeout(e, resultFuture, rpcRequestMessage, zRpcServiceBean);
        }

    }

    public Object doMockMethod() {

        String mockMethod = zRpcServiceBean.getMockMethod();
        try {
            Class<?> aClass = Class.forName(mockMethod);
            Object instance = aClass.newInstance();
            Method method = aClass.getMethod(MOCK_METHOD_NAME);
            return method.invoke(instance);

        } catch (Exception ex) {
            throw new RuntimeException(ex.getMessage());
        }

    }


    private void checkResult(ZRpcRequestMessage rpcRequest, ZRpcResponseMessage<Object> rpcResponse) {


        if (rpcResponse == null) {
            throw new RuntimeException("call service but rpcResponse is null");
        }
        if (!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) {
            throw new RuntimeException("request id not equal response id");
        }

        if (rpcResponse.getStatueCode() == null || !rpcResponse.getStatueCode().equals(ZRpcResponseEnum.SUCCESS.getStatueCode())) {
            throw new RuntimeException("call service fail");
        }

    }

    @Override
    public void publishEvent(ZRpcEvent event) {
        simpleZRpcEventMulticaster.multicastEvent(event);
    }
}
